import cv2
import numpy as np
import serial
import time
import os
import json
import abc
from datetime import datetime
from typing import List, Dict, Tuple, Any, Optional

# ====================== 配置管理模块 ======================
class ConfigManager:
    """配置管理类，负责加载和保存配置"""
    
    def __init__(self, config_file="config.json"):
        self.config_file = config_file
        self.config = self._load_default_config()
        
    def _load_default_config(self) -> Dict:
        """加载默认配置"""
        return {
            "camera": {
                "source_type": "uvc",  # 默认使用UVC
                "uvc": {
                    "id": 0,
                    "width": 1280,
                    "height": 720
                },
                "rtsp": {
                    "url": "",
                    "width": 1280,
                    "height": 720
                }
            },
            "serial": {
                "port": "COM3",
                "baud_rate": 9600,
                "timeout": 1
            },
            "device": {
                "startup_time": 15,
                "power_cycle_wait_time": 2
            },
            "detection": {
                "brightness_threshold": 30,
                "black_pixel_ratio": 0.5,
                "regions": [
                    {"x": 100, "y": 100, "width": 300, "height": 200, "name": "主屏幕"},
                    {"x": 500, "y": 100, "width": 200, "height": 150, "name": "副屏幕"}
                ]
            },
            "output": {
                "screenshot_dir": "screenshots"
            }
        }
    
    def load(self) -> Dict:
        """从文件加载配置，如果文件不存在则使用默认配置"""
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    self.config = json.load(f)
                print(f"已从 {self.config_file} 加载配置")
            else:
                print(f"配置文件 {self.config_file} 不存在，使用默认配置")
                self.save()  # 保存默认配置
        except Exception as e:
            print(f"加载配置失败: {e}，使用默认配置")
        return self.config
    
    def save(self) -> bool:
        """保存配置到文件"""
        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, ensure_ascii=False, indent=4)
            print(f"配置已保存到 {self.config_file}")
            return True
        except Exception as e:
            print(f"保存配置失败: {e}")
            return False
    
    def get(self, section: str, key: str = None):
        """获取配置项"""
        if key is None:
            return self.config.get(section, {})
        return self.config.get(section, {}).get(key)
    
    def set(self, section: str, key: str, value: Any) -> None:
        """设置配置项"""
        if section not in self.config:
            self.config[section] = {}
        self.config[section][key] = value

# ====================== 电源控制模块 ======================
class PowerControlInterface(abc.ABC):
    """电源控制接口，定义电源控制的基本方法"""
    
    @abc.abstractmethod
    def connect(self) -> bool:
        """连接到电源控制设备"""
        pass
    
    @abc.abstractmethod
    def disconnect(self) -> None:
        """断开与电源控制设备的连接"""
        pass
    
    @abc.abstractmethod
    def power_on(self) -> bool:
        """给设备上电"""
        pass
    
    @abc.abstractmethod
    def power_off(self) -> bool:
        """给设备断电"""
        pass

class SerialRelayController(PowerControlInterface):
    """通过串口继电器控制电源"""
    
    def __init__(self, port: str, baud_rate: int, timeout: int = 1, relay_commands: Dict = None):
        self.port = port
        self.baud_rate = baud_rate
        self.timeout = timeout
        self.ser = None
        
        # 从配置中读取继电器命令，如果没有提供则使用默认值
        if relay_commands and "power_on" in relay_commands and "power_off" in relay_commands:
            self.power_on_cmd = bytes(relay_commands["power_on"])
            self.power_off_cmd = bytes(relay_commands["power_off"])
        else:
            # 默认继电器命令
            self.power_on_cmd = bytes([0xFE, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x98, 0x35])
            self.power_off_cmd = bytes([0xFE, 0x05, 0x00, 0x00, 0x00, 0x00, 0xD9, 0xC5])
    
    def connect(self) -> bool:
        """连接到串口"""
        try:
            self.ser = serial.Serial(self.port, self.baud_rate, timeout=self.timeout)
            print(f"成功连接到串口 {self.port}")
            return True
        except Exception as e:
            print(f"串口连接失败: {e}")
            return False
    
    def disconnect(self) -> None:
        """断开串口连接"""
        if self.ser and self.ser.is_open:
            self.ser.close()
            print("串口连接已关闭")
    
    def power_on(self) -> bool:
        """通过继电器给设备上电"""
        if not self.ser or not self.ser.is_open:
            print("错误: 串口未连接")
            return False
        try:
            self.ser.write(self.power_on_cmd)
            print("设备上电命令已发送")
            return True
        except Exception as e:
            print(f"发送上电命令失败: {e}")
            return False
    
    def power_off(self) -> bool:
        """通过继电器给设备断电"""
        if not self.ser or not self.ser.is_open:
            print("错误: 串口未连接")
            return False
        try:
            self.ser.write(self.power_off_cmd)
            print("设备断电命令已发送")
            return True
        except Exception as e:
            print(f"发送断电命令失败: {e}")
            return False

# ====================== 图像采集模块 ======================
class ImageCaptureInterface(abc.ABC):
    """图像采集接口，定义图像采集的基本方法"""
    
    @abc.abstractmethod
    def open(self) -> bool:
        """打开图像采集设备"""
        pass
    
    @abc.abstractmethod
    def close(self) -> None:
        """关闭图像采集设备"""
        pass
    
    @abc.abstractmethod
    def capture(self) -> Optional[np.ndarray]:
        """捕获一帧图像"""
        pass

class UVCCapture(ImageCaptureInterface): # 原 CameraCapture，可以考虑重命名
    """使用UVC摄像头捕获图像"""
    
    def __init__(self, camera_id: int, width: int = 0, height: int = 0):
        self.camera_id = camera_id
        self.width = width
        self.height = height
        self.cap = None
    
    def open(self) -> bool:
        """打开摄像头"""
        try:
            self.cap = cv2.VideoCapture(self.camera_id, cv2.CAP_DSHOW) # 明确使用 DirectShow 后端，有时可以改善UVC兼容性
            if not self.cap.isOpened():
                print(f"无法打开UVC摄像头 ID: {self.camera_id}")
                return False
            
            # 设置分辨率
            if self.width > 0 and self.height > 0:
                self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
                self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
            
            print(f"UVC摄像头 ID: {self.camera_id} 已打开")
            return True
        except Exception as e:
            print(f"打开UVC摄像头失败: {e}")
            return False
    
    def close(self) -> None:
        """关闭摄像头"""
        if self.cap:
            self.cap.release()
            print("UVC摄像头已关闭")
    
    def capture(self) -> Optional[np.ndarray]:
        """从摄像头捕获一帧图像"""
        if not self.cap or not self.cap.isOpened():
            print("错误: UVC摄像头未打开")
            return None
        
        ret, frame = self.cap.read()
        if not ret:
            print("无法从UVC摄像头捕获图像")
            return None
        
        return frame

class RTSPCapture(ImageCaptureInterface):
    """使用RTSP流捕获图像"""

    def __init__(self, rtsp_url: str, width: int = 0, height: int = 0):
        self.rtsp_url = rtsp_url
        self.width = width
        self.height = height
        self.cap = None

    def open(self) -> bool:
        """打开RTSP流"""
        if not self.rtsp_url:
            print("错误: RTSP URL为空")
            return False
        try:
            # 使用FFMPEG后端可能会提高RTSP流的兼容性
            self.cap = cv2.VideoCapture(self.rtsp_url, cv2.CAP_FFMPEG)
            if not self.cap.isOpened():
                print(f"无法打开RTSP流: {self.rtsp_url}")
                return False
            
            actual_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            actual_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
            print(f"RTSP流已打开，分辨率: {actual_width}x{actual_height}")
            return True
        except Exception as e:
            print(f"打开RTSP流失败: {e}")
            return False

    def close(self) -> None:
        """关闭RTSP流"""
        if self.cap:
            self.cap.release()
            print("RTSP流已关闭")

    def capture(self) -> Optional[np.ndarray]:
        """从RTSP流捕获一帧图像"""
        if not self.cap or not self.cap.isOpened():
            print("错误: RTSP流未打开")
            return None
        
        ret, frame = self.cap.read()
        if not ret:
            print("无法从RTSP流捕获图像")
            return None
        
        # 如果需要调整分辨率
        if self.width > 0 and self.height > 0 and (frame.shape[1] != self.width or frame.shape[0] != self.height):
            frame = cv2.resize(frame, (self.width, self.height))
            
        return frame

# ====================== 图像分析模块 ======================
class DetectionRegion:
    """检测区域类，表示一个需要检测的屏幕区域"""
    
    def __init__(self, x: int, y: int, width: int, height: int, name: str):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.name = name
    
    def get_coordinates(self) -> Tuple[int, int, int, int]:
        """获取区域坐标"""
        return (self.x, self.y, self.width, self.height)

class AnalysisInterface(abc.ABC):
    """图像分析接口，定义图像分析的基本方法"""
    
    @abc.abstractmethod
    def analyze(self, frame: np.ndarray) -> Tuple[List[Dict], np.ndarray]:
        """分析图像，返回分析结果和标记后的图像"""
        pass

class BrightnessAnalyzer(AnalysisInterface):
    """亮度分析器，分析图像中各个区域的亮度"""
    
    def __init__(self, regions: List[DetectionRegion], threshold: int = 30, black_pixel_ratio: float = 0.5):
        self.regions = regions
        self.threshold = threshold
        self.black_pixel_ratio = black_pixel_ratio
    
    def analyze(self, frame: np.ndarray) -> Tuple[List[Dict], np.ndarray]:
        """分析图像中各个检测区域的亮度"""
        results = []
        
        for region in self.regions:
            x, y, w, h = region.get_coordinates()
            name = region.name
            
            # 提取区域
            region_img = frame[y:y+h, x:x+w]
            
            # 转换为灰度图
            gray_region = cv2.cvtColor(region_img, cv2.COLOR_BGR2GRAY)
            
            # 计算黑色像素的数量
            black_pixels = np.sum(gray_region < self.threshold)
            total_pixels = gray_region.size
            black_ratio = black_pixels / total_pixels
            
            # 判断是否为黑屏
            is_black = black_ratio > self.black_pixel_ratio
            
            # 计算平均亮度
            avg_brightness = np.mean(gray_region)
            
            results.append({
                "region_name": name,
                "coordinates": (x, y, w, h),
                "is_black": is_black,
                "black_pixel_ratio": black_ratio,
                "avg_brightness": avg_brightness
            })
            
            # 在图像上标记检测区域
            color = (0, 0, 255) if is_black else (0, 255, 0)  # 红色表示黑屏，绿色表示正常
            cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
            cv2.putText(frame, f"{name}: {'黑屏' if is_black else '正常'}", 
                       (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
            
        return results, frame

# ====================== 输出模块 ======================
class OutputInterface(abc.ABC):
    """输出接口，定义输出结果的基本方法"""
    
    @abc.abstractmethod
    def save_result(self, frame: np.ndarray, results: List[Dict]) -> str:
        """保存结果，返回保存路径"""
        pass
    
    @abc.abstractmethod
    def print_result(self, results: List[Dict]) -> None:
        """打印结果"""
        pass

class ScreenshotOutput(OutputInterface):
    """截图输出，将结果保存为截图"""
    
    def __init__(self, output_dir: str = "screenshots"):
        self.output_dir = output_dir
        os.makedirs(self.output_dir, exist_ok=True)
    
    def save_result(self, frame: np.ndarray, results: List[Dict]) -> str:
        """保存截图"""
        # 检查是否有黑屏
        has_black_screen = any(result["is_black"] for result in results)
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        status = "black_screen" if has_black_screen else "normal"
        filename = f"{self.output_dir}/screen_shots_{timestamp}_{status}.jpg"
        cv2.imwrite(filename, frame)
        print(f"截图已保存: {filename}")
        return filename
    
    def print_result(self, results: List[Dict]) -> None:
        """打印检测结果"""
        print("\n检测结果:")
        for result in results:
            print(f"{result['region_name']}: {'black_screen' if result['is_black'] else 'naomal'} "
                  f"(黑色像素比例: {result['black_pixel_ratio']:.2f}, 平均亮度: {result['avg_brightness']:.2f})")

# ====================== 测试流程控制模块 ======================
class TestController:
    """测试控制器，负责协调各个模块完成测试流程"""
    
    def __init__(self, config_manager: ConfigManager):
        self.config = config_manager.load()
        
        # 初始化电源控制模块
        serial_config = self.config["serial"]
        self.power_controller = SerialRelayController(
            port=serial_config["port"],
            baud_rate=serial_config["baud_rate"],
            timeout=serial_config["timeout"],
            relay_commands=serial_config.get("relay_commands")
        )
        
        # 初始化图像采集模块 - 根据配置选择源
        camera_config = self.config["camera"]
        source_type = camera_config.get("source_type", "uvc").lower()

        if source_type == "rtsp":
            # 使用RTSP配置
            rtsp_config = camera_config.get("rtsp", {})
            print("使用RTSP拉流模式")
            self.image_capturer = RTSPCapture(
                rtsp_url=rtsp_config.get("url", ""),
                width=rtsp_config.get("width", 0),
                height=rtsp_config.get("height", 0)
            )
        else:  # 默认使用UVC
            # 使用UVC配置
            uvc_config = camera_config.get("uvc", {})
            print("使用UVC拉流模式")
            self.image_capturer = CameraCapture(
                camera_id=uvc_config.get("id", 0),
                width=uvc_config.get("width", 0),
                height=uvc_config.get("height", 0)
            )
        
        # 初始化检测区域
        detection_config = self.config["detection"]
        self.regions = []
        for region in detection_config["regions"]:
            self.regions.append(DetectionRegion(
                x=region["x"],
                y=region["y"],
                width=region["width"],
                height=region["height"],
                name=region["name"]
            ))
        
        # 添加日志输出，显示加载了哪些区域
        print(f"已加载 {len(self.regions)} 个检测区域:")
        for i, region in enumerate(self.regions):
            print(f"  {i+1}. {region.name} ({region.x}, {region.y}, {region.width}, {region.height})")
        
        # 初始化图像分析模块
        self.analyzer = BrightnessAnalyzer(
            regions=self.regions,
            threshold=detection_config["brightness_threshold"],
            black_pixel_ratio=detection_config["black_pixel_ratio"]
        )
        
        # 初始化输出模块
        output_config = self.config["output"]
        self.output = ScreenshotOutput(output_dir=output_config["screenshot_dir"])
        
        # 设备参数
        device_config = self.config["device"]
        self.device_startup_time = device_config["startup_time"]
        self.power_cycle_wait_time = device_config["power_cycle_wait_time"]
        self.test_cycles = device_config["test_cycles"]
    
    def run_single_test(self) -> Tuple[bool, List[Dict], np.ndarray]:
        """运行单次测试，返回测试结果、分析结果和图像"""
        # 打开摄像头
        if not self.image_capturer.open():
            return False, [], None
        
        # 捕获图像
        frame = self.image_capturer.capture()
        self.image_capturer.close()
        
        if frame is None:
            return False, [], None
        
        # 分析图像
        results, marked_frame = self.analyzer.analyze(frame)
        
        # 检查是否有黑屏
        has_black_screen = any(result["is_black"] for result in results)
        
        return not has_black_screen, results, marked_frame
    
    def run_test(self) -> bool:
        """运行完整测试流程"""
        # 检查是否有检测区域
        if not self.regions:
            print("错误: 未设置检测区域")
            return False
        
        # 连接电源控制器
        if not self.power_controller.connect():
            print("错误: 无法连接电源控制器，测试终止")
            return False
        time_cycles_init = 0 
        try:
            while time_cycles_init < self.test_cycles:
                # 第一次测试循环
                print("开始第一次测试...")
                print("发送上电指令")
                self.power_controller.power_on()
                print(f"等待设备启动 {self.device_startup_time} 秒...")
                time.sleep(self.device_startup_time)
                success, results, frame = self.run_single_test()
                #输出结果
                self.output.print_result(results)
                self.output.save_result(frame, results)
                time_cycles_init  = time_cycles_init + 1
                if not success:
                    print("检测到黑屏，测试终止")
                    self.power_controller.power_off()
                    return False
                print("发送断电指令")
                self.power_controller.power_off()
                print(f"等待 {self.power_cycle_wait_time} 秒...")
                time.sleep(self.power_cycle_wait_time)
                if time_cycles_init - 1 == self.test_cycles:
                    print("测试完成")
                    return True
        finally:
            # 断开电源控制器连接
            self.power_controller.disconnect()
        

# ====================== 主程序 ======================
def main():
    """主程序入口"""
    # 加载配置
    config_manager = ConfigManager("config.json")
    
    # 创建测试控制器
    controller = TestController(config_manager)
    
    # 运行测试
    controller.run_test()

if __name__ == "__main__":
    main()